package com.threegroup.admin.controller;

import com.threegroup.admin.dto.LoginDTO;
import com.threegroup.admin.security.exception.UserRoleNotFoundException;
import com.threegroup.admin.security.jwt.Audience;
import com.threegroup.admin.security.jwt.JwtTokenUtil;
import com.threegroup.admin.security.user.SecurityUser;
import com.threegroup.admin.service.ICaptchaService;
import com.threegroup.common.constant.Constant;
import com.threegroup.common.exception.ErrorCode;
import com.threegroup.common.redis.RedisConf;
import com.threegroup.common.redis.RedisUtils;
import com.threegroup.common.utils.IpUtils;
import com.threegroup.common.utils.JsonUtils;
import com.threegroup.common.utils.Result;
import com.threegroup.common.utils.StringUtils;
import com.threegroup.common.validator.AssertUtils;
import com.threegroup.common.validator.ValidatorUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 登录管理
 */
@RestController
@Slf4j
public class LoginController {

    /**
     * 12小时后过期
     */
    private final static int EXPIRE = 3600 * 12;

    @Resource
    private ICaptchaService captchaService;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private Audience audience;

    /**
     * 获取验证码
     * @param response
     * @param uuid
     * @throws IOException
     */
    @GetMapping("/captcha")
    public void getCaptcha(HttpServletResponse response, String uuid) throws IOException {
        AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);

        captchaService.create(response, uuid);
    }

    /**
     * 用户登录
     * @param request
     * @param login
     * @return
     */
    @PostMapping("/login")
    public Result login(HttpServletRequest request, @RequestBody LoginDTO login) {

        // 校验数据
        ValidatorUtils.validateEntity(login);

        // 验证码
        boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha());
        if (!flag) {
            return new Result().error(ErrorCode.CAPTCHA_ERROR);
        }

        String ip = IpUtils.getIpAddr(request);
        String limitCount = (String) redisUtils.get(RedisConf.LOGIN_LIMIT + RedisConf.SEGMENTATION + ip);

        if (!StringUtils.isEmpty(limitCount)) {
            Integer tempLimitCount = Integer.valueOf(limitCount);
            if (tempLimitCount >= 5) {
                return new Result().error(ErrorCode.ACCOUNT_LOCK, "密码输入错误次数过多，已被锁定30分钟");
            }
        }

        Result resultInfo = new Result().error("未知错误");

        try {
            Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()));
            // 执行登录
            SecurityContextHolder.getContext().setAuthentication(auth);
            // 获取登录信息
            SecurityUser admin = (SecurityUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            String jwtToken = jwtTokenUtil.createJwt(
                    String.valueOf(admin.getUid()),
                    admin.getUsername(),
                    ip,
                    audience.getClientId(),
                    audience.getName(),
                    EXPIRE * 1000,
                    audience.getBase64Secret());
            String token = Constant.TOKEN_HEADER + jwtToken;
            // 将新的 Token 存入 Redis 中
            redisUtils.setEx(String.format("%s:%s", RedisConf.LOGIN_TOKEN_KEY, token), JsonUtils.objectToJson(admin), EXPIRE, TimeUnit.SECONDS);

            Map<String, Object> result = new HashMap<>(2);
            result.put(Constant.TOKEN_HEADER, token);
            result.put("expire", EXPIRE);
            resultInfo = resultInfo.ok(result);
        } catch (UsernameNotFoundException e) {
            resultInfo.error(ErrorCode.ACCOUNT_PASSWORD_ERROR, e.getMessage());
        } catch (UserRoleNotFoundException e) {
            resultInfo.error(ErrorCode.ACCOUNT_PASSWORD_ERROR, e.getMessage());
        } catch (BadCredentialsException e) {
            //出现密码错误异常时 将redis中错误次数+1
            resultInfo.error(ErrorCode.ACCOUNT_PASSWORD_ERROR, String.format("用户名或密码错误，错误%d次后，账户将被锁定30分钟", setLoginCommit(request)));
        } finally {
            return resultInfo;
        }
    }


    /**
     * 退出登录
     * @return
     */
    @PostMapping("/logout")
    public Result logout() {
        ServletRequestAttributes attribute = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attribute.getRequest();
        String token = request.getAttribute(Constant.TOKEN_HEADER).toString();

        if (StringUtils.isEmpty(token)) {
            return new Result().error(ErrorCode.TOKEN_INVALID);
        }

        // 移除Redis中的用户
        redisUtils.delete(String.format("%s:%s", RedisConf.LOGIN_TOKEN_KEY, token));
        //清除SecurityContextHolder上下文内容
        SecurityContextHolder.clearContext();

        return new Result();
    }


    /**
     * 设置登录限制，返回剩余次数
     * 密码错误五次，将会锁定30分钟
     *
     * @param request
     */
    private Integer setLoginCommit(HttpServletRequest request) {
        String ip = IpUtils.getIpAddr(request);
        String count = (String) redisUtils.get(RedisConf.LOGIN_LIMIT + RedisConf.SEGMENTATION + ip);
        Integer surplusCount = 5;
        if (!StringUtils.isEmpty(count)) {
            Integer countTemp = Integer.valueOf(count) + 1;
            surplusCount = surplusCount - countTemp;
            redisUtils.setEx(RedisConf.LOGIN_LIMIT + RedisConf.SEGMENTATION + ip, String.valueOf(countTemp), 30, TimeUnit.MINUTES);
        } else {
            surplusCount = surplusCount - 1;
            redisUtils.setEx(RedisConf.LOGIN_LIMIT + RedisConf.SEGMENTATION + ip, Constant.STR_ONE, 30, TimeUnit.MINUTES);
        }
        return surplusCount;
    }
}
